package com.tianji.learning.service.impl;

import com.tianji.common.autoconfigure.mq.RabbitMqHelper;
import com.tianji.common.constants.MqConstants;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.learning.constants.RedisConstants;
import com.tianji.learning.entity.mq.SignInMessage;
import com.tianji.learning.entity.vo.SignResultVO;
import com.tianji.learning.service.ISignRecordsService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.List;

@Service
@Slf4j
@RequiredArgsConstructor
public class SignRecordsServiceImpl implements ISignRecordsService {

    private final StringRedisTemplate stringRedisTemplate;

    private final RabbitMqHelper mqHelper;

    /**
     * 签到
     *
     * @return
     */
    @Override
    public SignResultVO addSignRecord() {
        // 1、获取用户id
        Long userId = UserContext.getUser();

        // 2、拼接key
//        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMM");
        LocalDateTime now = LocalDateTime.now();// 当前时间的年月
        String format = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));

        String key = RedisConstants.SIGN_RECORD_KEY_PREFIX + userId + format;

        // 利用bitmap存储用户签到记录，key为用户id，value为bitmap，bitmap的每一位代表一天，1代表签到，0代表未签到
        // 偏移量就是当前日期减去1
        int dayOfMonth = now.getDayOfMonth() - 1;
        System.out.println("dayOfMonth = " + dayOfMonth);
        Boolean aBoolean = stringRedisTemplate.opsForValue().setBit(key, dayOfMonth, true);
        // 3、判断是否已经签到
        if (Boolean.TRUE.equals(aBoolean)) {
            // 说明已经签到
            throw new BizIllegalException("不能重复签到");
        }

        // TODO 4.计算连续签到天数
        // 返回值为10进制的数字，转换为2进制，从右往左数，第一个0的位置就是连续签到的天数
        // 就是与1进行与运算，如果结果为1，说明当前位为1，如果结果为0，说明当前位为0
        // 例如：1010，与1进行与运算，结果为0，说明第一位为0，不是连续签到，与10进行与运算，结果为1，说明第二位为1，是连续签到

        // constantSignCount就是连续签到的天数
        int countConstantSignDays = countConstantSignDays(key, dayOfMonth);

        // TODO 判断是否需要加积分
        int rewardPoints = 0; // 连续签到的奖励积分
        if (countConstantSignDays == 7) {
            rewardPoints = 10;
        } else if (countConstantSignDays == 14) {
            rewardPoints = 20;
        } else if (countConstantSignDays == 28) {
            rewardPoints = 40;
        }

        // TODO 保存积分
        // 发送消息到mq，异步保存积分
        mqHelper.send(
                MqConstants.Exchange.LEARNING_EXCHANGE,
                MqConstants.Key.SIGN_IN,
                SignInMessage.of(userId, rewardPoints + 1));// 签到积分是基本得分+奖励积分

        // 封装返回结果
        SignResultVO signResultVO = new SignResultVO();
        signResultVO.setSignDays(countConstantSignDays);
        signResultVO.setRewardPoints(rewardPoints);

        return signResultVO;
    }

    /**
     * 查询签到记录
     *
     * @return
     */
    @Override
    public Integer[] querySignRecords() {
        Long userId = UserContext.getUser();
        // 拼接key
        LocalDateTime now = LocalDateTime.now();
        // 取出多少位
        int dayOfMonth = now.getDayOfMonth();

        String key = RedisConstants.SIGN_RECORD_KEY_PREFIX + userId + now.format(DateTimeFormatter.ofPattern(":yyyyMM"));
        List<Long> result = stringRedisTemplate.opsForValue().bitField(key, BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0));
        if (CollUtils.isEmpty(result)) {
            return new Integer[0];
        }
        // 将数组转换为集合
        int sign = result.get(0).intValue();
        int pos = dayOfMonth - 1;
        Integer[] arr = new Integer[dayOfMonth];
        Arrays.fill(arr, 0);
        while (sign != 0) {
            int i = (sign & 1);
            if (i == 1) {
                arr[pos--] = 1;
            }
            sign = sign >>> 1;
        }
        return arr;
    }

    /**
     * 计算连续签到天数
     *
     * @param key
     * @param dayOfMonth
     * @return
     */
    private int countConstantSignDays(String key, int dayOfMonth) {
        List<Long> sign = stringRedisTemplate.opsForValue().bitField(key, BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0));
        if (CollUtils.isEmpty(sign)) {
            return 0;
        }
        // 下面开始计算连续签到天数
        Long signRecord = sign.get(0);
        int constantSignCount = 0;
        while ((signRecord & 1) != 0) {
            constantSignCount++;
            // 因为是无符号数，是三个>>>，如果是有符号数，是两个>>
            signRecord = signRecord >>> 1;
        }
        return constantSignCount;
    }
}
